/*
 *
 */
package com.ePadink;


import java.io.InputStream;
import java.io.PrintStream;

public class MD5
{
  public static final int MD5_DIGESTSIZE = 16;
  int[] state;
  int[] count;
  byte[] buffer;
  private static final int S11 = 7;
  private static final int S12 = 12;
  private static final int S13 = 17;
  private static final int S14 = 22;
  private static final int S21 = 5;
  private static final int S22 = 9;
  private static final int S23 = 14;
  private static final int S24 = 20;
  private static final int S31 = 4;
  private static final int S32 = 11;
  private static final int S33 = 16;
  private static final int S34 = 23;
  private static final int S41 = 6;
  private static final int S42 = 10;
  private static final int S43 = 15;
  private static final int S44 = 21;

  private static int F(int x, int y, int z)
  {
    return x & y | (x ^ 0xFFFFFFFF) & z;
  }

  private static int G(int x, int y, int z) {
    return x & z | y & (z ^ 0xFFFFFFFF);
  }

  private static int H(int x, int y, int z) {
    return x ^ y ^ z;
  }

  private static int I(int x, int y, int z) {
    return y ^ (x | z ^ 0xFFFFFFFF);
  }

  private static int rotateLeft(int x, int n)
  {
    return x << n | x >>> 32 - n;
  }

  private static int FF(int a, int b, int c, int d, int x, int s, int ac)
  {
    a += F(b, c, d) + x + ac;
    a = rotateLeft(a, s);
    a += b;
    return a;
  }

  private static int GG(int a, int b, int c, int d, int x, int s, int ac) {
    a += G(b, c, d) + x + ac;
    a = rotateLeft(a, s);
    a += b;
    return a;
  }

  private static int HH(int a, int b, int c, int d, int x, int s, int ac) {
    a += H(b, c, d) + x + ac;
    a = rotateLeft(a, s);
    a += b;
    return a;
  }

  private static int II(int a, int b, int c, int d, int x, int s, int ac) {
    a += I(b, c, d) + x + ac;
    a = rotateLeft(a, s);
    a += b;
    return a;
  }

  public MD5() {
    reset();
  }

  public void reset() {
    this.state = new int[4];
    this.count = new int[2];
    this.buffer = new byte[64];

    this.state[0] = 1732584193;
    this.state[1] = -271733879;
    this.state[2] = -1732584194;
    this.state[3] = 271733878;
  }

  public void update(byte[] pInput) {
    update(pInput, pInput.length);
  }

  private void update(byte[] pInput, int lNumOfBytes)
  {
    int lIndex = this.count[0] >> 3 & 0x3F;

//    if (this.count[0] += (lNumOfBytes << 3) < lNumOfBytes << 3)
//      this.count[1] += 1;
//    this.count[1] += (lNumOfBytes >> 29);

//    int lPartLen = 64 - lIndex;
    int lI;
//    if (lNumOfBytes >= lPartLen) {
//      System.arraycopy(pInput, 0, this.buffer, lIndex, lPartLen);
//      transform(this.buffer, 0);
//      for (int lI = lPartLen; lI + 63 < lNumOfBytes; lI += 64)
//        transform(pInput, lI);
//      lIndex = 0;
//    }
//    else {
      lI = 0;
//    }

    System.arraycopy(pInput, lI, this.buffer, lIndex, lNumOfBytes - lI);
  }

  public byte[] digest() {
    byte[] digest = new byte[16];

    byte[] PADDING = new byte[64];
    PADDING[0] = -128;

    byte[] bits = new byte[8];

    encode(bits, this.count, 8);

    int lIndex = this.count[0] >> 3 & 0x3F;
    int lPadLen = lIndex < 56 ? 56 - lIndex : 120 - lIndex;
    update(PADDING, lPadLen);

    update(bits, 8);

    encode(digest, this.state, 16);

    return digest;
  }

  void transform(byte[] block, int fromIndex)
  {
    int lA = this.state[0]; int lB = this.state[1]; int lC = this.state[2]; int lD = this.state[3];
    int[] x = new int[16];

    decode(x, block, fromIndex, 64);

    lA = FF(lA, lB, lC, lD, x[0], 7, -680876936);
    lD = FF(lD, lA, lB, lC, x[1], 12, -389564586);
    lC = FF(lC, lD, lA, lB, x[2], 17, 606105819);
    lB = FF(lB, lC, lD, lA, x[3], 22, -1044525330);
    lA = FF(lA, lB, lC, lD, x[4], 7, -176418897);
    lD = FF(lD, lA, lB, lC, x[5], 12, 1200080426);
    lC = FF(lC, lD, lA, lB, x[6], 17, -1473231341);
    lB = FF(lB, lC, lD, lA, x[7], 22, -45705983);
    lA = FF(lA, lB, lC, lD, x[8], 7, 1770035416);
    lD = FF(lD, lA, lB, lC, x[9], 12, -1958414417);
    lC = FF(lC, lD, lA, lB, x[10], 17, -42063);
    lB = FF(lB, lC, lD, lA, x[11], 22, -1990404162);
    lA = FF(lA, lB, lC, lD, x[12], 7, 1804603682);
    lD = FF(lD, lA, lB, lC, x[13], 12, -40341101);
    lC = FF(lC, lD, lA, lB, x[14], 17, -1502002290);
    lB = FF(lB, lC, lD, lA, x[15], 22, 1236535329);

    lA = GG(lA, lB, lC, lD, x[1], 5, -165796510);
    lD = GG(lD, lA, lB, lC, x[6], 9, -1069501632);
    lC = GG(lC, lD, lA, lB, x[11], 14, 643717713);
    lB = GG(lB, lC, lD, lA, x[0], 20, -373897302);
    lA = GG(lA, lB, lC, lD, x[5], 5, -701558691);
    lD = GG(lD, lA, lB, lC, x[10], 9, 38016083);
    lC = GG(lC, lD, lA, lB, x[15], 14, -660478335);
    lB = GG(lB, lC, lD, lA, x[4], 20, -405537848);
    lA = GG(lA, lB, lC, lD, x[9], 5, 568446438);
    lD = GG(lD, lA, lB, lC, x[14], 9, -1019803690);
    lC = GG(lC, lD, lA, lB, x[3], 14, -187363961);
    lB = GG(lB, lC, lD, lA, x[8], 20, 1163531501);
    lA = GG(lA, lB, lC, lD, x[13], 5, -1444681467);
    lD = GG(lD, lA, lB, lC, x[2], 9, -51403784);
    lC = GG(lC, lD, lA, lB, x[7], 14, 1735328473);
    lB = GG(lB, lC, lD, lA, x[12], 20, -1926607734);

    lA = HH(lA, lB, lC, lD, x[5], 4, -378558);
    lD = HH(lD, lA, lB, lC, x[8], 11, -2022574463);
    lC = HH(lC, lD, lA, lB, x[11], 16, 1839030562);
    lB = HH(lB, lC, lD, lA, x[14], 23, -35309556);
    lA = HH(lA, lB, lC, lD, x[1], 4, -1530992060);
    lD = HH(lD, lA, lB, lC, x[4], 11, 1272893353);
    lC = HH(lC, lD, lA, lB, x[7], 16, -155497632);
    lB = HH(lB, lC, lD, lA, x[10], 23, -1094730640);
    lA = HH(lA, lB, lC, lD, x[13], 4, 681279174);
    lD = HH(lD, lA, lB, lC, x[0], 11, -358537222);
    lC = HH(lC, lD, lA, lB, x[3], 16, -722521979);
    lB = HH(lB, lC, lD, lA, x[6], 23, 76029189);
    lA = HH(lA, lB, lC, lD, x[9], 4, -640364487);
    lD = HH(lD, lA, lB, lC, x[12], 11, -421815835);
    lC = HH(lC, lD, lA, lB, x[15], 16, 530742520);
    lB = HH(lB, lC, lD, lA, x[2], 23, -995338651);

    lA = II(lA, lB, lC, lD, x[0], 6, -198630844);
    lD = II(lD, lA, lB, lC, x[7], 10, 1126891415);
    lC = II(lC, lD, lA, lB, x[14], 15, -1416354905);
    lB = II(lB, lC, lD, lA, x[5], 21, -57434055);
    lA = II(lA, lB, lC, lD, x[12], 6, 1700485571);
    lD = II(lD, lA, lB, lC, x[3], 10, -1894986606);
    lC = II(lC, lD, lA, lB, x[10], 15, -1051523);
    lB = II(lB, lC, lD, lA, x[1], 21, -2054922799);
    lA = II(lA, lB, lC, lD, x[8], 6, 1873313359);
    lD = II(lD, lA, lB, lC, x[15], 10, -30611744);
    lC = II(lC, lD, lA, lB, x[6], 15, -1560198380);
    lB = II(lB, lC, lD, lA, x[13], 21, 1309151649);
    lA = II(lA, lB, lC, lD, x[4], 6, -145523070);
    lD = II(lD, lA, lB, lC, x[11], 10, -1120210379);
    lC = II(lC, lD, lA, lB, x[2], 15, 718787259);
    lB = II(lB, lC, lD, lA, x[9], 21, -343485551);

    this.state[0] += lA;
    this.state[1] += lB;
    this.state[2] += lC;
    this.state[3] += lD;
  }

  static void encode(byte[] pOutput, int[] pInput, int lLen)
  {
    int lI = 0; for (int lJ = 0; lJ < lLen; lJ += 4) {
      pOutput[lJ] = ((byte)(pInput[lI] & 0xFF));
      pOutput[(lJ + 1)] = ((byte)(pInput[lI] >> 8 & 0xFF));
      pOutput[(lJ + 2)] = ((byte)(pInput[lI] >> 16 & 0xFF));
      pOutput[(lJ + 3)] = ((byte)(pInput[lI] >> 24 & 0xFF));

      lI++;
    }
  }

  private void decode(int[] pOutput, byte[] pInput, int fromIndex, int lLen)
  {
    int lI = 0; for (int lJ = 0; lJ < lLen; lJ += 4) {
      pOutput[lI] = (pInput[(lJ + fromIndex)] & 0xFF | (pInput[(lJ + fromIndex + 1)] & 0xFF) << 8 | (pInput[(lJ + fromIndex + 2)] & 0xFF) << 16 | (pInput[(lJ + fromIndex + 3)] & 0xFF) << 24);

      lI++;
    }
  }

  private static boolean compareArrays(byte[] b1, byte[] b2, byte[] b3)
  {
    for (int i = 0; i < b1.length; i++) {
      System.out.println(b1[i] + "\t" + b2[i] + "\t" + b3[i]);
      if (b1[i] != b2[i])
        return false;
    }
    System.out.println("Press Enter to continue...");
    try {
      System.in.read();
    } catch (Exception e) {
    }
    return true;
  }
}